package com.jse;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Version;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandler;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Base64;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;

import com.jse.json.Json;

@SuppressWarnings("unchecked")
public class Http {

	private final static Log log = Log.get("Http");
	public static final int HTTP_CONTINUE = 100;
	public static final int HTTP_OK = 200;
	public static final int HTTP_CREATED = 201;
	public static final int HTTP_ACCEPTED = 202;
	public static final int HTTP_NOT_AUTHORITATIVE = 203;
	public static final int HTTP_NO_CONTENT = 204;
	public static final int HTTP_RESET = 205;
	public static final int HTTP_PARTIAL = 206;
	public static final int HTTP_MULT_CHOICE = 300;
	public static final int HTTP_MOVED_PERM = 301;
	public static final int HTTP_MOVED_TEMP = 302;
	public static final int HTTP_SEE_OTHER = 303;
	public static final int HTTP_NOT_MODIFIED = 304;
	public static final int HTTP_USE_PROXY = 305;
	public static final int HTTP_BAD_REQUEST = 400;
	public static final int HTTP_UNAUTHORIZED = 401;
	public static final int HTTP_PAYMENT_REQUIRED = 402;
	public static final int HTTP_FORBIDDEN = 403;
	public static final int HTTP_NOT_FOUND = 404;
	public static final int HTTP_BAD_METHOD = 405;
	public static final int HTTP_NOT_ACCEPTABLE = 406;
	public static final int HTTP_PROXY_AUTH = 407;
	public static final int HTTP_CLIENT_TIMEOUT = 408;
	public static final int HTTP_CONFLICT = 409;
	public static final int HTTP_GONE = 410;
	public static final int HTTP_LENGTH_REQUIRED = 411;
	public static final int HTTP_PRECON_FAILED = 412;
	public static final int HTTP_ENTITY_TOO_LARGE = 413;
	public static final int HTTP_REQ_TOO_LONG = 414;
	public static final int HTTP_UNSUPPORTED_TYPE = 415;
	public static final int HTTP_INTERNAL_ERROR = 500;
	public static final int HTTP_NOT_IMPLEMENTED = 501;
	public static final int HTTP_BAD_GATEWAY = 502;
	public static final int HTTP_UNAVAILABLE = 503;
	public static final int HTTP_GATEWAY_TIMEOUT = 504;
	public static final int HTTP_VERSION = 505;

	public static final String CRLF = "\r\n";
	public static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.76";

	public static Object send(String method, String url, Object body) {
		return send(null, method, url, body, 0, null, false, null);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers) {
		return send(null, method, url, body, 0, headers, false, null);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers, Object type) {
		return send(null, method, url, body, 0, headers, false, type);
	}

	public static Object send(String method, String url, Object body, Map<String, String> headers, boolean async,
			Object type) {
		return send(null, method, url, body, 0, headers, async, type);
	}

	public static Object send(Version verion, String method, String url, Object body, int timeout,
			Map<String, String> headers, boolean async, Object type) {
		try {
			HttpRequest.Builder build = HttpRequest.newBuilder(URI.create(url));
			if (timeout != 0)
				build.timeout(Duration.ofMillis(timeout));
			if (verion == null)
				verion = Version.HTTP_1_1;
			build.version(verion);
			if (headers == null)
				headers = Map.of();
			headers.forEach((k, v) -> {
				build.setHeader(k.toString(), v.toString());
			});
			if (!headers.containsKey("User-Agent")) {
				build.setHeader("User-Agent", USER_AGENT);
			}
			if (!headers.containsKey("referer"))
				build.setHeader("referer", "http://jse.jdk17.cn");
			BodyPublisher bp = null;
			if (body == null) {
				bp = BodyPublishers.noBody();
			} else if (body instanceof File f) {
				bp = BodyPublishers.ofFile(f.toPath());
			} else if (body instanceof Path p) {
				bp = BodyPublishers.ofFile(p);
			} else if (body instanceof byte[] bs) {
				bp = BodyPublishers.ofByteArray(bs);
			} else if (body instanceof InputStream in) {
				bp = BodyPublishers.ofInputStream(() -> in);
			} else if (body instanceof Collection<?> c) {// body 为list则只支持json与xml
				var j = Json.jsonArray(c);
				String contentType = headers.get("Content-Type").toString();
				if (contentType.startsWith("application/xml") || contentType.startsWith("text/xml")) {
					bp = BodyPublishers.ofString(j.toXml());
				} else {
					bp = BodyPublishers.ofString(j.toString());// 普通文本处理
				}
			} else if (body instanceof Map<?, ?> map) {
				String content_Type = headers.get("Content-Type").toString();
				if (content_Type.startsWith("application/json")) {
					bp = BodyPublishers.ofString(Json.toJson(map));
				} else if (content_Type.startsWith("application/xml") || content_Type.startsWith("text/xml")) {
					bp = BodyPublishers.ofString(Json.jsonObject(map).toXml());
				} else if (content_Type.startsWith("multipart/form-data; boundary=")) {
					String boundary = headers.get("Content-Type").toString().substring(30).trim();
					Map<String, String> textMap = new HashMap<>();// 普通表单参数
					Map<String, File> fileMap = new HashMap<>();
					for (Object k : map.keySet()) {
						Object v = map.get(k);
						if (v == null) {
							textMap.put(k.toString(), "");
						} else if (v instanceof File f) {
							fileMap.put(k.toString(), f);
						} else {
							textMap.put(k.toString(), v.toString());
						}
					}
					StringBuilder sb = new StringBuilder();
					ByteArrayOutputStream out = new ByteArrayOutputStream();
					textMap.forEach((k, v) -> {// 写普通表单参数
						sb.append("--").append(boundary).append(CRLF)// 写分隔符--${boundary}，并回车换行
								// 写描述信息：Content-Disposition: form-data; name="参数名"，并两个回车换行
								.append(String.format("Content-Disposition: form-data; name=\"%s\"", k)).append(CRLF)
								.append(CRLF).append(v).append(CRLF);// 写具体内容：参数值，并回车换行
					});
					out.write(sb.toString().getBytes());
					sb.setLength(0);
					var base64 = headers.get("base64");
					fileMap.forEach((k, v) -> {// 写文件参数
						try (InputStream in = new FileInputStream(v)) {
							sb.append("--").append(boundary).append(CRLF);
							String fileName = v.getName();
							String contentDispositionStr = String.format(
									"Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"", k, fileName) + CRLF;
							sb.append(contentDispositionStr);
							String suffix = Fs.suffix(fileName);
							String mimeType = Fs.mimeType(fileName, "application/octet-stream");
							String contentType = "Content-Type: " + mimeType + CRLF + CRLF;
							sb.append(contentType);
							out.write(sb.toString().getBytes());
							sb.setLength(0);
							if (base64 != null// jpg,png,jpeg,psd,gif,bmp,webp
									&& suffix.equals(base64)) {
								String img = "data:" + mimeType + ";base64," + Awts.base64(in, suffix);
								out.write(img.getBytes());
							} else {
								out.write(in.readAllBytes());
							}
							out.write(CRLF.getBytes());// 回车换行
						} catch (Exception e) {
							e.printStackTrace();
						}
					});
					out.write((CRLF + "--" + boundary + "--" + CRLF).getBytes());// 回车换行
					bp = BodyPublishers.ofInputStream(() -> {// 转换formData
						return new ByteArrayInputStream(out.toByteArray());
					});
				} else {
					StringBuilder sb = new StringBuilder();// 普通类型
					map.forEach((k, v) -> {
						sb.append("&").append(k).append("=").append(v);
					});
					bp = BodyPublishers.ofString(sb.substring(1));
				}
			} else {
				String s = body.toString();
				if (s.length() > 0 && s.length() < 255 && (s.charAt(0) != '{' || s.charAt(0) != '[')
						&& (s.charAt(0) == '/' || s.charAt(1) == ':') && Fs.exists(s)) {
					bp = BodyPublishers.ofFile(Path.of(s));
				} else {
					bp = BodyPublishers.ofString(body.toString());
				}
			}
			build.method(method, bp);
			BodyHandler<?> bh = BodyHandlers.ofString();
			if (type == null) {
				bh = BodyHandlers.ofString(StandardCharsets.UTF_8);
			} else if (type instanceof String s) {
				bh = BodyHandlers.ofByteArray();
				if (s.equals("byte")) {
					bh = BodyHandlers.ofByteArray();
				} else if (s.equals("in")) {
					bh = BodyHandlers.ofInputStream();
				} else if (s.equals("stream")) {
					bh = BodyHandlers.ofLines();
				} else if (s.equals("pub")) {
					bh = BodyHandlers.ofPublisher();
				} else {
					bh = BodyHandlers.ofString(StandardCharsets.UTF_8);
				}
			} else if (type instanceof Path p) {
				if (Files.isDirectory(p)) {
					bh = BodyHandlers.ofFileDownload(p);
				} else {
					bh = BodyHandlers.ofFile(p);
				}
			} else if (type instanceof File f) {
				if (f.isDirectory()) {
					bh = BodyHandlers.ofFileDownload(f.toPath());
				} else {
					bh = BodyHandlers.ofFile(f.toPath());
				}
			} else if (type instanceof Charset charset) {
				bh = BodyHandlers.ofString(charset);
			} else if (type instanceof Consumer c) {// Consumer<Optional<byte[]>> consumer
				bh = BodyHandlers.ofByteArrayConsumer(c);
			}
			if (async) {
				return HttpClient.newHttpClient().sendAsync(build.build(), bh).get();
			} else {
				return HttpClient.newHttpClient().send(build.build(), bh);
			}
		} catch (IOException | InterruptedException | ExecutionException e) {
			log.warn("%s %s err:%s", method, url, e.getMessage());
		}
		return null;
	}

	public static String get(String url) {
		return ((HttpResponse<String>) send("GET", url, null)).body();
	}

	public static String get(String url, Map<?, ?> param) {
		return ((HttpResponse<String>) send("GET", url, param)).body();
	}

	public static String post(String url, Map<?, ?> param) {
		return ((HttpResponse<String>) send("POST", url, param)).body();
	}

	public static String postJson(String url, String json) {
		return ((HttpResponse<String>) send("POST", url, json, Map.of("Content-Type", "application/json"))).body();
	}

	public static String postJson(String url, Map<String,Object> json) {
		Map<String,String> header=new HashMap<String,String>();
		String type="application/json";
		if(json.containsKey("#Content-Type"))type=json.remove("#Content-Type").toString();
		header.put("Content-Type", type);
		Iterator<Map.Entry<String, Object>> it = json.entrySet().iterator();  
        while(it.hasNext()){  
            Entry<String, Object> entry = it.next();  
            String k=entry.getKey();
            if(k.charAt(0)=='#') {
            	Object v=entry.getValue();
            	header.put(k.substring(1),v.toString());
            	it.remove();
            }  
        }
		return ((HttpResponse<String>) send("POST", url, json,header)).body();
	}

	public static String postXml(String url, String xml) {
		return ((HttpResponse<String>) send("POST", url, xml, Map.of("Content-Type", "application/xml"))).body();
	}

	public static String postXml(String url, Map<?, ?> xml) {
		return ((HttpResponse<String>) send("POST", url, xml, Map.of("Content-Type", "application/xml"))).body();
	}

	public static void down(String url, String path) {
		down(url, path, true);
	}

	public static void down(String url, String path, boolean async) {
		send(Version.HTTP_1_1, "GET", url, null, 0, null, async, Path.of(path));
	}

	public static String basicAuth(String username, String password) {
		return "Basic " + Base64.getEncoder()
				.encodeToString(username.concat(":").concat(password).getBytes(StandardCharsets.UTF_8));
	}
	
	public static String msg(int code) {
	    switch (code) {
	      case HTTP_OK:
	        return " OK";
	      case HTTP_CONTINUE:
	        return " Continue";
	      case HTTP_CREATED:
	        return " Created";
	      case HTTP_ACCEPTED:
	        return " Accepted";
	      case HTTP_NOT_AUTHORITATIVE:
	        return " Non-Authoritative Information";
	      case HTTP_NO_CONTENT:
	        return " No Content";
	      case HTTP_RESET:
	        return " Reset Content";
	      case HTTP_PARTIAL:
	        return " Partial Content";
	      case HTTP_MULT_CHOICE:
	        return " Multiple Choices";
	      case HTTP_MOVED_PERM:
	        return " Moved Permanently";
	      case HTTP_MOVED_TEMP:
	        return " Temporary Redirect";
	      case HTTP_SEE_OTHER:
	        return " See Other";
	      case HTTP_NOT_MODIFIED:
	        return " Not Modified";
	      case HTTP_USE_PROXY:
	        return " Use Proxy";
	      case HTTP_BAD_REQUEST:
	        return " Bad Request";
	      case HTTP_UNAUTHORIZED:
	        return " Unauthorized";
	      case HTTP_PAYMENT_REQUIRED:
	        return " Payment Required";
	      case HTTP_FORBIDDEN:
	        return " Forbidden";
	      case HTTP_NOT_FOUND:
	        return " Not Found";
	      case HTTP_BAD_METHOD:
	        return " Method Not Allowed";
	      case HTTP_NOT_ACCEPTABLE:
	        return " Not Acceptable";
	      case HTTP_PROXY_AUTH:
	        return " Proxy Authentication Required";
	      case HTTP_CLIENT_TIMEOUT:
	        return " Request Time-Out";
	      case HTTP_CONFLICT:
	        return " Conflict";
	      case HTTP_GONE:
	        return " Gone";
	      case HTTP_LENGTH_REQUIRED:
	        return " Length Required";
	      case HTTP_PRECON_FAILED:
	        return " Precondition Failed";
	      case HTTP_ENTITY_TOO_LARGE:
	        return " Request Entity Too Large";
	      case HTTP_REQ_TOO_LONG:
	        return " Request-URI Too Large";
	      case HTTP_UNSUPPORTED_TYPE:
	        return " Unsupported Media Type";
	      case HTTP_INTERNAL_ERROR:
	        return " Internal Server Error";
	      case HTTP_NOT_IMPLEMENTED:
	        return " Not Implemented";
	      case HTTP_BAD_GATEWAY:
	        return " Bad Gateway";
	      case HTTP_UNAVAILABLE:
	        return " Service Unavailable";
	      case HTTP_GATEWAY_TIMEOUT:
	        return " Gateway Timeout";
	      case HTTP_VERSION:
	        return " HTTP Version Not Supported";
	      default:
	        return " ";
	    }
	  }

	public static void main(String[] args) throws InterruptedException {
//		down("https://cn.bing.com/th?id=OJ.ilrwlLiKV5AQIA&w=80&h=80&c=8&rs=1&pid=academic","d:/1.png",true);
//		Thread.sleep(2000l);
		System.out.println(basicAuth("zs", "123456"));
	}
}
